Skip to content

feat(angular): angular adapter and utils#368

Open
riccardoperra wants to merge 20 commits intoTanStack:mainfrom
riccardoperra:feat/angular-adapter
Open

feat(angular): angular adapter and utils#368
riccardoperra wants to merge 20 commits intoTanStack:mainfrom
riccardoperra:feat/angular-adapter

Conversation

@riccardoperra
Copy link

@riccardoperra riccardoperra commented Mar 4, 2026

The following PR continues the work of #364 to improve and fix angular integration:

  • devtool component can now render component, lazy components (by dynamic imports) or custom functions
  • devtools utils will now use a non-decoration approach to render components

Added some examples:

  • Basic: just a copy of "custom plugin" example from react
  • Panel: example using createAngularPlugin with a "vanilla" plugin example
  • with-devtools: example using sub package @tanstack/angular-devtools/provider for those who prefers injecting devtools with Dependency Injection (and automatically lazy-load devtool component)

Summary by CodeRabbit

Release Notes

  • New Features

    • Added full Angular framework support with new @tanstack/angular-devtools package
    • Angular integration now available via provider pattern with provideTanStackDevtools()
  • Documentation

    • Added comprehensive Angular setup guide and API reference
    • Introduced custom plugin development guide for Angular
    • Added quick-start examples and installation instructions for Angular 19+
  • Examples

    • Created three complete Angular example projects demonstrating basic setup, custom panels, and full devtools integration

@changeset-bot
Copy link

changeset-bot bot commented Mar 4, 2026

⚠️ No Changeset found

Latest commit: 4ebeb9e

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@riccardoperra riccardoperra force-pushed the feat/angular-adapter branch from a9d3f16 to 3349a19 Compare March 4, 2026 22:10
@nx-cloud
Copy link

nx-cloud bot commented Mar 5, 2026

🤖 Nx Cloud AI Fix Eligible

An automatically generated fix could have helped fix failing tasks for this run, but Self-healing CI is disabled for this workspace. Visit workspace settings to enable it and get automatic fixes in future runs.

To disable these notifications, a workspace admin can disable them in workspace settings.


View your CI Pipeline Execution ↗ for commit dc62a0d

Command Status Duration Result
nx affected --targets=test:eslint,test:sherif,t... ❌ Failed 2m 55s View ↗

☁️ Nx Cloud last updated this comment at 2026-03-11 12:22:30 UTC

@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 5, 2026

More templates

@tanstack/angular-devtools

npm i https://pkg.pr.new/TanStack/devtools/@tanstack/angular-devtools@368

@tanstack/devtools

npm i https://pkg.pr.new/TanStack/devtools/@tanstack/devtools@368

@tanstack/devtools-client

npm i https://pkg.pr.new/TanStack/devtools/@tanstack/devtools-client@368

@tanstack/devtools-ui

npm i https://pkg.pr.new/TanStack/devtools/@tanstack/devtools-ui@368

@tanstack/devtools-utils

npm i https://pkg.pr.new/TanStack/devtools/@tanstack/devtools-utils@368

@tanstack/devtools-vite

npm i https://pkg.pr.new/TanStack/devtools/@tanstack/devtools-vite@368

@tanstack/devtools-event-bus

npm i https://pkg.pr.new/TanStack/devtools/@tanstack/devtools-event-bus@368

@tanstack/devtools-event-client

npm i https://pkg.pr.new/TanStack/devtools/@tanstack/devtools-event-client@368

@tanstack/preact-devtools

npm i https://pkg.pr.new/TanStack/devtools/@tanstack/preact-devtools@368

@tanstack/react-devtools

npm i https://pkg.pr.new/TanStack/devtools/@tanstack/react-devtools@368

@tanstack/solid-devtools

npm i https://pkg.pr.new/TanStack/devtools/@tanstack/solid-devtools@368

@tanstack/vue-devtools

npm i https://pkg.pr.new/TanStack/devtools/@tanstack/vue-devtools@368

commit: a1d4364

@riccardoperra riccardoperra force-pushed the feat/angular-adapter branch from d1cafc8 to 3f27799 Compare March 8, 2026 08:45
@riccardoperra riccardoperra changed the base branch from worktree-kind-orbiting-dolphin to main March 8, 2026 12:26
@riccardoperra riccardoperra force-pushed the feat/angular-adapter branch from d82d6a2 to efa3afd Compare March 8, 2026 12:31
@riccardoperra riccardoperra marked this pull request as ready for review March 8, 2026 13:06
@riccardoperra riccardoperra changed the title feat(angular): angular adapter, add utils and some examples, fix build feat(angular): angular adapter and utils Mar 8, 2026
@riccardoperra riccardoperra force-pushed the feat/angular-adapter branch from 5abbcba to e5e5ec1 Compare March 8, 2026 14:05
@riccardoperra
Copy link
Author

riccardoperra commented Mar 8, 2026

@AlemTuzlak integration is done. I probably need pipelines so I can check with pkg.pr.new if everything works with some other devtools like table which is not published yet (unfortunately I always have a lot of issues running pnpm link with angular 😄)

@AlemTuzlak
Copy link
Collaborator

@riccardoperra mind just fixing conflicts and we can get this merged

AlemTuzlak and others added 13 commits March 11, 2026 12:37
Add the @tanstack/angular-devtools package with:
- TanStackDevtoolsComponent: standalone Angular component that wraps TanStackDevtoolsCore
- Plugin system supporting Angular components rendered into devtools containers
- Type definitions for Angular-specific plugin and init configuration
- Build config using @analogjs/vite-plugin-angular for AOT compilation
- Remove @analogjs/vite-plugin-angular from build (produces empty chunks
  for library builds). Use plain esbuild via Vite instead - Angular AOT
  compilation happens at consumer's build time.
- Upgrade Angular dev deps from ^19 to ^20 for TypeScript 5.9 compat.
- Remove @angular/build dep (only needed by analogjs plugin).
- Add @tanstack/angular-devtools workspace override to root.
- Fix array-type ESLint rule in angular-devtools
- Fix import/consistent-type-specifier-style in devtools-utils angular
- Sort peerDependencies alphabetically (sherif)
- Remove unused devDependencies flagged by knip
- Remove angularCompilerOptions from tsconfig (not using ngc)
Since angular-devtools already run the code within afterNextRender in browser env, we don't like to wrap the panel returned from utils
@riccardoperra riccardoperra force-pushed the feat/angular-adapter branch 2 times, most recently from c2ffbb9 to 084d4b1 Compare March 11, 2026 11:46
@riccardoperra
Copy link
Author

@AlemTuzlak done!

@AlemTuzlak
Copy link
Collaborator

Seems some stuff is failing

@riccardoperra
Copy link
Author

@AlemTuzlak fixed, i tested in my local env and all 28 tasks succeded

pnpm nx affected --targets=test:eslint,test:sherif,test:knip,test:lib,test:types,test:build,build

Anyway, I noticed while testing my examples that warning. I saw "sideEffects" false in core but the generated dist contains some import './chunkname'

Screenshot 2026-03-11 at 13 50 19

@coderabbitai
Copy link

coderabbitai bot commented Mar 14, 2026

📝 Walkthrough

Walkthrough

This pull request introduces comprehensive Angular support to TanStack Devtools, adding documentation, example projects, and a new @tanstack/angular-devtools package that integrates Angular component rendering, signal-based plugin management, and event bus communication for the devtools panel.

Changes

Cohort / File(s) Summary
Documentation - Configuration
docs/config.json, docs/installation.md, docs/quick-start.md, docs/devtools-utils.md
Added Angular framework entries to documentation navigation and installation guides with setup instructions, plugin configuration, and integration examples mirroring existing Vue/React/Preact/Solid patterns.
Documentation - Framework Guides
docs/framework/angular/basic-setup.md, docs/framework/angular/adapter.md, docs/framework/angular/guides/custom-plugins.md
Comprehensive Angular-specific documentation covering installation, adapter configuration, component integration, custom plugin development with EventClient, and debugging patterns.
Example Project - Basic
examples/angular/basic/*
Complete Angular CLI project demonstrating counter implementation with TanStack Devtools integration, including configuration files, app structure, and custom devtools panel with event client.
Example Project - Panel
examples/angular/panel/*
Angular project showcasing advanced devtools panel development with custom plugin creation, event subscription, and dynamic UI updates tied to application state.
Example Project - With Devtools
examples/angular/with-devtools/*
Full-featured example with provider-based devtools setup, dynamic plugin management, and application configuration for immediate devtools initialization.
Package - Angular Devtools Core
packages/angular-devtools/src/devtools.ts, packages/angular-devtools/src/types.ts
Core TanStackDevtools Angular component with lifecycle management, plugin rendering, component injection, input binding, and comprehensive type definitions for plugin system.
Package - Angular Devtools Provider
packages/angular-devtools/provider/with-devtools.ts
Angular-specific provider function with dependency injection tokens, signal-based plugin management, dynamic component rendering, and environment initialization logic for seamless integration into Angular applications.
Package - Build Configuration
packages/angular-devtools/package.json, packages/angular-devtools/ng-package.json, packages/angular-devtools/tsconfig*, packages/angular-devtools/eslint.config.js, packages/angular-devtools/vite.config.ts
Complete build and packaging setup for Angular devtools package using ng-packagr with ESM distribution, TypeScript strict mode, and dual exports for main and provider entry points.
Package - Devtools Utils Updates
packages/devtools-utils/src/angular/panel.ts, packages/devtools-utils/src/angular/plugin.ts, packages/devtools-utils/package.json, packages/devtools-utils/vite.config.angular.ts
Angular-specific utilities for creating panels and plugins with constructor/async factory support, theme management, and lifecycle handling alongside existing framework support.
Root Configuration
package.json, knip.json
Added workspace alias for Angular devtools package and knip configuration entry for unused code detection in the new package.

Sequence Diagram(s)

sequenceDiagram
    participant App as Angular App
    participant Provider as provideTanStackDevtools
    participant Core as TanStackDevtools Component
    participant Plugins as Plugin System
    participant EventBus as EventClient
    participant Renderer as Component Renderer

    App->>Provider: Bootstrap with appConfig
    Provider->>Provider: Create DEVTOOLS_OPTIONS_SIGNAL
    Provider->>Provider: Initialize TANSTACK_DEVTOOLS_PLUGINS signal
    
    Provider->>Core: Attach to ApplicationRef
    Core->>Core: afterNextRender hook
    Core->>Plugins: getPluginsMap(plugins)
    
    loop For Each Plugin
        Core->>Renderer: convertPlugin(pluginDef)
        Renderer->>Renderer: Resolve render (sync/async)
        Renderer->>Renderer: createComponent or functional render
        Renderer->>Core: Return rendered component ref
        Core->>Core: attachComponentRef (setup inputs/lifecycle)
        Core->>Core: Track in componentRefs map
    end
    
    Core->>Core: Initialize TanStackDevtoolsCore
    Core->>Core: Mount to host element
    
    App->>EventBus: Emit 'counter-state' event
    EventBus->>Plugins: Dispatch event to subscribers
    Plugins->>Renderer: Update component inputs via effect
    Renderer->>App: Reflect state changes in UI
    
    App->>Plugins: Update plugins signal
    Core->>Core: Watch for plugins change
    Core->>Renderer: destroyComponentsInContainer(old)
    Core->>Plugins: Re-render updated plugins
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

🐰 Hops of joy in Angular's embrace,
Devtools now fitted for this grand place,
Signals and injectors dance in a line,
Plugins and panels in sync they align,
From basic to fancy, the examples now shine!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description provides clear context about improvements to Angular integration, component rendering options, and new examples, but the author did not follow the template checklist. Complete the description template by checking the Contributing guide box and confirming local testing with pnpm test:pr, and indicate release impact with changeset status.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding an Angular adapter and utilities for TanStack Devtools.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (19)
examples/angular/with-devtools/src/index.html-5-5 (1)

5-5: ⚠️ Potential issue | 🟡 Minor

Title does not match the example name.

The title is "Basic" but this example is "with-devtools". This appears to be a copy-paste artifact from another example.

📝 Suggested fix
-    <title>Basic</title>
+    <title>With Devtools</title>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/angular/with-devtools/src/index.html` at line 5, The page title in
index.html is incorrect ("Basic"); update the <title> element in the file so it
matches the example name ("with-devtools" or a human-friendly variant like "With
Devtools") to avoid the copy-paste artifact and ensure the example
metadata/title reflects this example.
examples/angular/with-devtools/.editorconfig-11-17 (1)

11-17: ⚠️ Potential issue | 🟡 Minor

Remove the redundant IntelliJ-specific TypeScript setting.

The ij_typescript_use_double_quotes = false setting at line 13 is redundant with the standard quote_type = single setting above it. Since this .editorconfig sets root = true, it's independent of the parent config and doesn't need to maintain consistency with it. However, the duplicate setting should be removed for clarity:

Remove redundant IntelliJ setting
 [*.ts]
 quote_type = single
-ij_typescript_use_double_quotes = false
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/angular/with-devtools/.editorconfig` around lines 11 - 17, Remove
the redundant IntelliJ-specific TypeScript setting by deleting the line
"ij_typescript_use_double_quotes = false" from the file; keep the standard
"[*.ts]" block with "quote_type = single" as the single source of truth for
quote style and remove only the "ij_typescript_use_double_quotes" entry so there
is no duplicate configuration.
examples/angular/panel/src/index.html-5-5 (1)

5-5: ⚠️ Potential issue | 🟡 Minor

Update the page title to match the panel example.

<title>Basic</title> looks like a copy/paste mismatch for this panel app and can be confusing in browser tabs.

Suggested fix
-    <title>Basic</title>
+    <title>Panel</title>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/angular/panel/src/index.html` at line 5, Update the HTML title in
examples/angular/panel/src/index.html: replace the current <title>Basic</title>
with a more accurate title reflecting the app (for example "Angular Panel
Example" or "Panel") so the browser tab and metadata correctly identify this
panel demo; modify the <title> tag content only to match the panel example name.
examples/angular/panel/.vscode/launch.json-9-16 (1)

9-16: ⚠️ Potential issue | 🟡 Minor

preLaunchTask references npm but project uses pnpm.

The preLaunchTask values use "npm: start" and "npm: test", but the package.json specifies pnpm@10.24.0 as the package manager. VS Code may not recognize these tasks correctly. Consider updating to match the project's package manager or ensure VS Code's task auto-detection handles this.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/angular/panel/.vscode/launch.json` around lines 9 - 16, Update the
VS Code launch configuration so the preLaunchTask matches the project's package
manager: replace the "preLaunchTask" values referencing "npm: start" and "npm:
test" with the corresponding "pnpm: start" and "pnpm: test" (or ensure tasks
with those names exist), targeting the entries that define the Chrome launches
("name": "ng serve"/implicit block and "name": "ng test") so VS Code will run
the correct pnpm tasks before launching.
examples/angular/with-devtools/angular.json-12-13 (1)

12-13: ⚠️ Potential issue | 🟡 Minor

Project name should match the example folder.

The project is named "basic" but this configuration is in the with-devtools example directory. This appears to be a copy-paste oversight from another example. Consider renaming to "with-devtools" for consistency and to avoid confusion.

📝 Proposed fix
   "projects": {
-    "basic": {
+    "with-devtools": {
       "projectType": "application",

Also update build targets on lines 61 and 64:

-              "buildTarget": "basic:build:production"
+              "buildTarget": "with-devtools:build:production"
             },
             "development": {
-              "buildTarget": "basic:build:development"
+              "buildTarget": "with-devtools:build:development"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/angular/with-devtools/angular.json` around lines 12 - 13, The
project key in the angular.json "projects" object is incorrectly named "basic"
but should match the example folder; rename the project key from "basic" to
"with-devtools" and update every reference to that key inside the file (notably
the build and serve targets currently referencing "basic") so they point to
"with-devtools" instead; ensure target entries such as the "build" and "serve"
(and any "architect" or "targets" blocks) are updated to use the new project
name so configuration and CLI commands remain consistent.
examples/angular/panel/README.md-1-3 (1)

1-3: ⚠️ Potential issue | 🟡 Minor

Update README title to match the panel example directory.

The README title says "# Basic" but this file is in the panel example directory. The title should be "# Panel" for consistency with the directory name.

📝 Proposed fix
-# Basic
+# Panel
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/angular/panel/README.md` around lines 1 - 3, The README title
currently reads "Basic" but should match the example directory name; update the
top-level heading in the README.md by replacing the heading text "Basic" with
"Panel" so the file title reads "# Panel" for consistency with the panel example
directory.
docs/framework/angular/guides/custom-plugins.md-178-178 (1)

178-178: ⚠️ Potential issue | 🟡 Minor

Minor grammar: "built in" should be hyphenated.

📝 Suggested fix
-Both the `TanStackDevtools` and the TanStack `EventClient` come with built in debug mode which will log to the console the emitted event as well as the EventClient status.
+Both the `TanStackDevtools` and the TanStack `EventClient` come with built-in debug mode which will log to the console the emitted event as well as the EventClient status.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/framework/angular/guides/custom-plugins.md` at line 178, The phrase
"built in" in the sentence referencing TanStackDevtools and the TanStack
EventClient should be hyphenated; update the sentence so it reads "built-in
debug mode" where "built in" appears (affecting the line mentioning
TanStackDevtools and EventClient) to correct the grammar.
examples/angular/with-devtools/README.md-1-3 (1)

1-3: ⚠️ Potential issue | 🟡 Minor

README title mismatch with project directory.

The title says "Basic" but this file is in the with-devtools example directory. This appears to be a copy-paste oversight from the basic example.

📝 Suggested fix
-# Basic
+# With Devtools

-This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 21.2.0.
+This project was generated using [Angular CLI](https://github.com/angular/angular-cli).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/angular/with-devtools/README.md` around lines 1 - 3, The README
title currently reads "Basic" which doesn't match this example's directory;
update the top-level heading in examples/angular/with-devtools/README.md (the
line containing "# Basic") to a correct, descriptive title such as "#
with-devtools" or "# Angular with DevTools" so the file name and content reflect
the example's purpose.
docs/framework/angular/guides/custom-plugins.md-197-197 (1)

197-197: ⚠️ Potential issue | 🟡 Minor

Minor spelling: "Where as" should be "Whereas".

📝 Suggested fix
-Where as the EventClient's debug mode can be activated by:
+Whereas the EventClient's debug mode can be activated by:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/framework/angular/guides/custom-plugins.md` at line 197, Replace the
incorrect two-word phrase "Where as" with the correct single-word "Whereas" in
the docs/framework/angular/guides/custom-plugins.md content to fix the minor
spelling error.
docs/framework/angular/guides/custom-plugins.md-213-216 (1)

213-216: ⚠️ Potential issue | 🟡 Minor

Add language specifier to fenced code block.

The code block showing debug output should have a language specifier for consistent formatting.

📝 Suggested fix
-```
+```text
 [tanstack-devtools:client-bus] Initializing client event bus

 [tanstack-devtools:custom-devtools-plugin] Registered event to bus custom-devtools:counter-state
 ```
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/framework/angular/guides/custom-plugins.md` around lines 213 - 216, The
fenced code block showing debug output lacks a language specifier; update the
block in docs/framework/angular/guides/custom-plugins.md around the debug output
containing "[tanstack-devtools:client-bus] Initializing client event bus" and
"[tanstack-devtools:custom-devtools-plugin] Registered event to bus
custom-devtools:counter-state" by adding a language tag (e.g., ```text) after
the opening backticks so the block becomes a fenced code block with the "text"
specifier for consistent formatting.
docs/framework/angular/guides/custom-plugins.md-122-135 (1)

122-135: ⚠️ Potential issue | 🟡 Minor

Template incorrectly calls getter with parentheses.

In the template at line 125, stateHistory() is called with parentheses, but stateHistory is defined as a getter (line 133-135), not a method. Getters should be accessed without parentheses.

📝 Suggested fix
   template: `
     <div>
       <div>{{ state()?.count }}</div>
-      <div>{{ stateHistory() }}</div>
+      <div>{{ stateHistory }}</div>
     </div>
   `,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/framework/angular/guides/custom-plugins.md` around lines 122 - 135, The
template incorrectly invokes the getter stateHistory as a method; update the
template to reference the getter without parentheses (use {{ stateHistory }}
instead of {{ stateHistory() }}) so it matches the DevtoolPanel get
stateHistory() accessor and returns the computed JSON string properly.
docs/framework/angular/basic-setup.md-18-32 (1)

18-32: ⚠️ Potential issue | 🟡 Minor

Fix import name mismatch in code example.

The import statement on line 20 imports TanStackDevtoolsComponent, but the imports array on line 25 references TanStackDevtools. This inconsistency will cause a compilation error when users follow this documentation.

📝 Proposed fix
 ```typescript
 import { Component } from '@angular/core'
-import { TanStackDevtoolsComponent } from '@tanstack/angular-devtools'
+import { TanStackDevtools } from '@tanstack/angular-devtools'

 `@Component`({
   selector: 'app-root',
   standalone: true,
   imports: [TanStackDevtools],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/framework/angular/basic-setup.md` around lines 18 - 32, The import name
in the example is inconsistent: the file imports TanStackDevtoolsComponent but
the App component's standalone imports array references TanStackDevtools. Fix by
making the imported symbol match the one used in the App component — either
change the import to TanStackDevtools (recommended) or change the imports array
to use TanStackDevtoolsComponent so the imported identifier and the imports
entry are the same; update the import statement near the top that currently
names TanStackDevtoolsComponent accordingly.
docs/framework/angular/adapter.md-46-48 (1)

46-48: ⚠️ Potential issue | 🟡 Minor

Misleading text references non-existent component field.

Line 48 states the adapter "uses component ... instead of render", but the actual field name shown in the type definition and code examples is render. This appears to be outdated or incorrect.

📝 Proposed fix
-The Angular adapter uses `component` (an Angular component class reference) instead of `render` (a JSX element) in plugin definitions. Inputs are provided through the `inputs` field and bound to the component with `setInput()`, rather than being embedded directly in a JSX expression or passed via `v-bind`.
+The Angular adapter uses `render` with an Angular component class reference (rather than a JSX element) in plugin definitions. Inputs are provided through the `inputs` field and bound to the component with `setInput()`, rather than being embedded directly in a JSX expression or passed via `v-bind`.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/framework/angular/adapter.md` around lines 46 - 48, The sentence
incorrectly refers to a non-existent `component` field; update the documentation
text to reference the actual `render` field used in the adapter type and
examples (e.g., change "uses `component` (an Angular component class reference)
instead of `render`" to indicate that the adapter uses `render` and explain that
inputs are provided via the `inputs` field and bound with `setInput()`), or if
the implementation should instead use `component`, update the type/examples to
match—ensure consistency between the docs and the symbols `render`, `inputs`,
and `setInput()`.
docs/framework/angular/adapter.md-38-45 (1)

38-45: ⚠️ Potential issue | 🟡 Minor

Documentation table has incorrect type for render field.

The table states render has type Type<any>, but the type definition above (line 31) shows it should be TanStackDevtoolsAngularPluginRender. The description should align with the actual type definition for accuracy.

📝 Proposed fix
-| `render`      | `Type<any>` | The Angular component class to render as the plugin panel content. |
+| `render`      | `TanStackDevtoolsAngularPluginRender` | The Angular component class, lazy component (via dynamic import), or custom function to render as the plugin panel content. |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/framework/angular/adapter.md` around lines 38 - 45, The docs table
incorrectly types the render field as Type<any>; update the table entry for the
`render` row to use the actual type name `TanStackDevtoolsAngularPluginRender`
and adjust the description to reference that type (e.g., "The render property is
a TanStackDevtoolsAngularPluginRender that defines the Angular component/class
used for plugin panel content"); ensure the `render` column now matches the type
alias `TanStackDevtoolsAngularPluginRender` used in the code.
packages/angular-devtools/src/types.ts-44-62 (1)

44-62: ⚠️ Potential issue | 🟡 Minor

JSDoc example references component but type uses render.

The JSDoc comment states "Each plugin should have a component" and the example shows component: MyPluginComponent, but the actual TanStackDevtoolsAngularPlugin type at line 38 defines render, not component. This mismatch will confuse users.

📝 Proposed fix to align JSDoc with actual type
   /**
    * Array of plugins to be used in the devtools.
-   * Each plugin should have a `component` that is an Angular component class.
+   * Each plugin should have a `render` property that specifies an Angular component class or render function.
    *
    * Example:
    * ```typescript
    * `@Component`({
    *   template: `<tanstack-devtools [plugins]="plugins" />`,
    *   imports: [TanStackDevtoolsComponent],
    * })
    * class AppComponent {
    *   plugins: TanStackDevtoolsAngularPlugin[] = [
-   *     { name: 'My Plugin', component: MyPluginComponent }
+   *     { name: 'My Plugin', render: MyPluginComponent }
    *   ];
    * }
    * ```
    */
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/angular-devtools/src/types.ts` around lines 44 - 62, The JSDoc for
TanStackDevtoolsAngularInit and its example incorrectly refer to a `component`
property while the actual type TanStackDevtoolsAngularPlugin defines `render`;
update the JSDoc/example to use `render` (e.g., replace `{ name: 'My Plugin',
component: MyPluginComponent }` with `{ name: 'My Plugin', render:
MyPluginComponent }`) so the documentation matches the
TanStackDevtoolsAngularPlugin type and avoids confusion.
packages/devtools-utils/src/angular/panel.ts-53-57 (1)

53-57: ⚠️ Potential issue | 🟡 Minor

Add error handling for the lazy-loaded panel.

When CoreClass is a function returning a Promise (lazy loading), there's no .catch() handler. If the dynamic import fails, the error will be unhandled.

Proposed fix
         if (isConstructor) {
           mount(new CoreClass(inputs()))
         } else {
           CoreClass()
             .then((ResolvedCoreClass) => new ResolvedCoreClass(inputs()))
             .then(mount)
+            .catch((error) => {
+              console.error('[createAngularPanel] Failed to load panel:', error)
+            })
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/devtools-utils/src/angular/panel.ts` around lines 53 - 57, The
lazy-load branch calling CoreClass() lacks error handling; wrap the promise
chain starting at CoreClass() so rejections are caught and handled—add a .catch
handler to the CoreClass().then(...).then(mount) chain that logs the error
and/or invokes the existing mount/error handling flow (reference CoreClass,
inputs(), and mount) to avoid unhandled promise rejections and surface failures
during dynamic import.
packages/angular-devtools/provider/with-devtools.ts-75-80 (1)

75-80: ⚠️ Potential issue | 🟡 Minor

Use isPlatformBrowser() instead of string comparison for platform check.

The PLATFORM_ID value should be checked using Angular's isPlatformBrowser() utility from @angular/common rather than direct string comparison. The string 'browser' is not the documented API contract.

Proposed fix

Add to imports:

import { isPlatformBrowser } from '@angular/common'
-      if (inject(PLATFORM_ID) !== 'browser') {
+      if (!isPlatformBrowser(inject(PLATFORM_ID))) {
         return
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/angular-devtools/provider/with-devtools.ts` around lines 75 - 80,
Replace the direct string comparison of PLATFORM_ID with Angular's
isPlatformBrowser check: import isPlatformBrowser from '@angular/common' and
change the platform guard in the provideEnvironmentInitializer block (where
inject(PLATFORM_ID) is used) to use if (!isPlatformBrowser(inject(PLATFORM_ID)))
return; so the environment check uses the documented API rather than comparing
to the string 'browser'.
packages/angular-devtools/provider/with-devtools.ts-91-103 (1)

91-103: ⚠️ Potential issue | 🟡 Minor

Add error handling for the dynamic import.

The dynamic import of @tanstack/angular-devtools has no .catch() handler. If the import fails (e.g., network error, bundler misconfiguration), the error will be silently swallowed or result in an unhandled promise rejection.

Proposed fix
       import('@tanstack/angular-devtools').then(({ TanStackDevtools }) => {
         const componentRef = createComponent(TanStackDevtools, {
           environmentInjector: injector,
           bindings: [
             inputBinding('config', config),
             inputBinding('plugins', plugins),
             inputBinding('eventBusConfig', eventBusConfig),
           ],
         })

         applicationRef.attachView(componentRef.hostView)
         componentRef.changeDetectorRef.detectChanges()
-      })
+      }).catch((error) => {
+        console.error('[`@tanstack/angular-devtools`] Failed to load devtools:', error)
+      })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/angular-devtools/provider/with-devtools.ts` around lines 91 - 103,
The dynamic import of '@tanstack/angular-devtools' in with-devtools.ts lacks
error handling; update the import promise chain that calls
import('@tanstack/angular-devtools').then(...) to add a .catch handler that logs
the error and a contextual message (e.g., "Failed to load TanStackDevtools") and
avoids leaving an unhandled rejection; reference the import call and the
createComponent/TanStackDevtools block so the catch can report the error and
optionally perform a safe fallback (e.g., skip attaching the devtools) if the
module fails to load.
packages/devtools-utils/src/angular/panel.ts-15-20 (1)

15-20: ⚠️ Potential issue | 🟡 Minor

Type guard using !!o.prototype is fragile.

This check relies on the fact that arrow functions and async functions lack a prototype property, while class constructors have one. However, regular functions also have prototype, so if someone passes a non-arrow function that returns a Promise, it would incorrectly be identified as a constructor.

Consider a more robust check:

Proposed fix
 function isPanelClassConstructor<
   TComponentProps extends DevtoolsPanelProps,
   TCoreDevtoolsClass extends BaseCorePanelClass,
 >(o: any): o is CoreClassConstructor<TComponentProps, TCoreDevtoolsClass> {
-  return !!o.prototype
+  // Classes have a prototype with a constructor property pointing back to themselves
+  return typeof o === 'function' && o.prototype?.constructor === o && !o.prototype[Symbol.toStringTag]
 }

Alternatively, if the lazy factory is always expected to be an arrow function or async function (which is typical for dynamic imports), the current approach works in practice but deserves a comment explaining the assumption.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/devtools-utils/src/angular/panel.ts` around lines 15 - 20, The
type-guard isPanelClassConstructor currently uses !!o.prototype which
misclassifies plain functions as class constructors; replace that heuristic with
a robust check that ensures o is an ES6 class constructor, e.g. verify typeof o
=== 'function' and use Function.prototype.toString.call(o) to test for a leading
"class" token (e.g. /^class\s/.test(...)); update the function
isPanelClassConstructor<TComponentProps, TCoreDevtoolsClass> to use this test
(or, if you intentionally only accept arrow/async factories, add a clear comment
explaining that assumption) so non-class functions are not treated as
constructors.
🧹 Nitpick comments (14)
examples/angular/with-devtools/.vscode/mcp.json (1)

6-6: Angular's official MCP documentation uses unpinned @angular/cli intentionally.

The unpinned version on line 6 aligns with Angular's documented setup for .vscode/mcp.json. Since the MCP server is experimental, Angular recommends tracking the latest CLI to pick up fixes and improvements rather than pinning.

While pinning to @angular/cli@20 would improve reproducibility across team members, it goes against Angular's official guidance for this feature. If your team prefers consistency and controlled upgrades, pinning is a valid choice, but it's not the documented approach.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/angular/with-devtools/.vscode/mcp.json` at line 6, The current args
entry ["-y", "@angular/cli", "mcp"] intentionally uses an unpinned `@angular/cli`
per Angular's MCP docs—leave it as-is to track latest fixes; if you instead want
reproducible behavior across the team, change that args element to
"@angular/cli@20" (i.e., replace "@angular/cli" with "@angular/cli@20") so the
MCP launch uses the pinned CLI version.
examples/angular/panel/package.json (1)

22-22: Inconsistent version specifier for @tanstack/devtools-event-client.

This dependency uses an exact version (0.4.2) while other @tanstack/* packages use caret ranges (^0.0.1, ^0.10.13, ^0.3.3). Consider using a caret range for consistency unless pinning is intentional.

Suggested fix
-    "@tanstack/devtools-event-client": "0.4.2",
+    "@tanstack/devtools-event-client": "^0.4.2",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/angular/panel/package.json` at line 22, The dependency
"@tanstack/devtools-event-client" is pinned to an exact version "0.4.2" while
other `@tanstack` packages in package.json use caret ranges; update the version
specifier for "@tanstack/devtools-event-client" to use a caret range (e.g.
"^0.4.2") to match the other `@tanstack/`* entries and maintain consistent semver
behavior across the project.
packages/devtools-utils/vite.config.angular.ts (1)

5-7: Drop the empty plugins field for clarity.

No behavior change, just removes noise in the config object.

♻️ Suggested cleanup
 const config = defineConfig({
-  plugins: [],
   test: {
     name: packageJson.name,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/devtools-utils/vite.config.angular.ts` around lines 5 - 7, The
config object created via defineConfig currently includes an unnecessary empty
"plugins" property; remove the "plugins: []" entry from the defineConfig call
(the config constant) to clean up the Vite configuration without changing
behavior—locate the defineConfig({ ... }) block that assigns to config and
delete the plugins key.
examples/angular/basic/src/app/devtools/custom-devtools-panel.ts (1)

27-28: Clarify the "Automatically added" comment.

The comment // Automatically added on the theme input is unclear. Consider elaborating on what "automatically adds" this input or remove the comment if it's no longer relevant.

📝 Suggested clarification
-  // Automatically added
+  // Injected by the devtools plugin framework
   readonly theme = input<string>()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/angular/basic/src/app/devtools/custom-devtools-panel.ts` around
lines 27 - 28, The "// Automatically added" comment above the readonly theme =
input<string>() declaration is ambiguous; either replace it with a clearer note
that states who/what adds this input and when (e.g., "Added by DevTools host at
runtime" or "Injected by framework build step") or remove the comment entirely
if it's stale; update the comment adjacent to the readonly theme =
input<string>() symbol to explicitly state its source and purpose so future
readers understand why the input exists.
examples/angular/panel/src/app/counter.ts (2)

10-28: Consider extracting duplicated emit logic.

The DevtoolsEventClient.emit('counter-state', ...) call is duplicated in both increment and decrement. Consider extracting this into a helper function for DRY code.

♻️ Proposed refactor
 export function createCounter() {
   const count = signal(0)
   const history: Array<number> = []
 
+  const emitState = () => {
+    DevtoolsEventClient.emit('counter-state', {
+      count: count(),
+      history,
+    })
+  }
+
   return {
     value: () => count(),
     increment: () => {
       count.update((n) => n + 1)
       history.push(count())
-
-      // The emit eventSuffix must match that of the EventMap defined in eventClient.ts
-      DevtoolsEventClient.emit('counter-state', {
-        count: count(),
-        history,
-      })
+      emitState()
     },
     decrement: () => {
       count.update((n) => n - 1)
       history.push(count())
-
-      DevtoolsEventClient.emit('counter-state', {
-        count: count(),
-        history,
-      })
+      emitState()
     },
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/angular/panel/src/app/counter.ts` around lines 10 - 28, increment
and decrement both call DevtoolsEventClient.emit('counter-state', { count:
count(), history }) causing duplicated emit logic; extract that into a small
helper (e.g., emitCounterState or notifyCounterState) that reads count() and
history and calls DevtoolsEventClient.emit('counter-state', payload), then
replace the duplicated emit calls in the increment and decrement functions with
a single call to the new helper; ensure the helper is colocated with the
count/history variables and used after updating history so payload stays
correct.

4-9: Consider emitting initial state.

The panel won't display any state until the user clicks increment or decrement for the first time. Consider emitting the initial state when the counter is created, or document this behavior as intentional.

💡 Optional: Emit initial state
 export function createCounter() {
   const count = signal(0)
   const history: Array<number> = []
+
+  // Emit initial state
+  DevtoolsEventClient.emit('counter-state', {
+    count: count(),
+    history,
+  })
 
   return {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/angular/panel/src/app/counter.ts` around lines 4 - 9, createCounter
currently never emits its initial value so the UI sees no state until the first
increment/decrement; fix it by emitting the initial state when the counter is
created: after initializing count and history in createCounter, record the
initial value (e.g., push count() into history) and invoke the same
notifier/emit function you use for subsequent updates (or expose/ call value()
once) so the panel receives the initial state; update references in
createCounter, count, history and the notifier/emit handler accordingly.
docs/framework/angular/basic-setup.md (1)

51-58: Class name inconsistency between examples.

The first example uses App as the class name (line 31), while this example uses AppComponent (line 51). For consistency within the same documentation page, consider using the same naming convention.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/framework/angular/basic-setup.md` around lines 51 - 58, The examples use
inconsistent class names — one declares class App and the later snippet declares
class AppComponent; update the later example so the class name matches the first
example (e.g., rename AppComponent to App) and ensure the export and any
internal references (plugins array, TanStackDevtoolsAngularPlugin,
AngularQueryDevtoolsPanel) are updated accordingly so both examples use the same
class identifier.
packages/angular-devtools/package.json (2)

55-63: Duplicate tslib in dependencies and devDependencies.

tslib is listed in both dependencies (line 57) and devDependencies (line 62). The runtime dependency listing is sufficient; the devDependencies entry is redundant.

♻️ Remove duplicate tslib
   "devDependencies": {
     "@angular/core": "^21.2.0",
-    "ng-packagr": "^21.2.0",
-    "tslib": "^2.3.0"
+    "ng-packagr": "^21.2.0"
   },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/angular-devtools/package.json` around lines 55 - 63, Remove the
duplicate tslib entry by keeping it under "dependencies" and deleting the
"tslib" entry from "devDependencies" in package.json; update the package.json
object so only the top-level "dependencies" contains "tslib" and ensure no other
references to tslib remain in dev tooling config (look for "tslib" in the
package.json dependencies/devDependencies blocks).

53-53: Build script uses Unix-specific commands.

The rm -rf commands in the build script are not cross-platform. This may cause issues for contributors on Windows.

♻️ Use cross-platform alternative

Consider using rimraf or the existing premove (used in the clean script) for cross-platform compatibility:

-    "build": "ng-packagr -p ng-package.json -c tsconfig.build.json && rm -rf ./dist/package.json && rm -rf ./dist/provider/package.json"
+    "build": "ng-packagr -p ng-package.json -c tsconfig.build.json && premove ./dist/package.json ./dist/provider/package.json"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/angular-devtools/package.json` at line 53, The build script in
package.json ("build") uses Unix-only rm -rf; replace those calls with a
cross-platform remover such as rimraf (or reuse the existing "premove" helper
used by "clean") so Windows contributors won't fail; update the "build" script
to call rimraf on ./dist/package.json and ./dist/provider/package.json (or call
the existing preremove command), and if using rimraf add it to devDependencies
and/or the scripts so CI and local installs have the tool available.
examples/angular/basic/src/app/app.ts (1)

19-21: Consider using a more meaningful defer trigger condition.

@defer (when true) is equivalent to an unconditional defer, which loads the content when the browser is idle. This works but the when true condition is redundant and potentially confusing.

♻️ Optional: Simplified defer syntax
-    `@defer` (when true) {
+    `@defer` {
       <tanstack-devtools [plugins]="plugins()" [eventBusConfig]="{ debug: true }" />
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/angular/basic/src/app/app.ts` around lines 19 - 21, The `@defer` block
uses an explicit always-true condition (`@defer` (when true)) which is redundant;
change it to use the simpler unconditional defer syntax or a meaningful
predicate instead. Update the `@defer` usage wrapping <tanstack-devtools
[plugins]="plugins()" [eventBusConfig]="{ debug: true }" /> by removing "(when
true)" or replacing it with a real trigger (e.g., a boolean like isIdle or
isDevMode) so the intent is clear and the defer behavior for tanstack-devtools
and plugins() is preserved.
examples/angular/panel/src/app/devtools/custom-devtools-panel.ts (1)

12-17: Consider using textContent for safer DOM updates (optional for example code).

The static analysis flagged the innerHTML usage. While in this example the data is trusted internal state (numeric count and array history), for production patterns consider using DOM APIs like textContent or a sanitization library.

For this example demonstrating devtools integration, the current approach is acceptable.

♻️ Optional: Safer DOM update pattern
-    function render(count: number, history: string) {
-      plugin.innerHTML = `
-       <div>counter state: ${count}</div>
-       <div>counter history: ${history}</div>
-      `
-    }
+    function render(count: number, history: string) {
+      plugin.textContent = ''
+      const countDiv = element.ownerDocument.createElement('div')
+      countDiv.textContent = `counter state: ${count}`
+      const historyDiv = element.ownerDocument.createElement('div')
+      historyDiv.textContent = `counter history: ${history}`
+      plugin.appendChild(countDiv)
+      plugin.appendChild(historyDiv)
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/angular/panel/src/app/devtools/custom-devtools-panel.ts` around
lines 12 - 17, The render(count: number, history: string) function writes
unescaped HTML into plugin via plugin.innerHTML which static analysis flagged;
change render to avoid innerHTML by creating or selecting child elements under
the plugin container and setting their textContent (or concatenating safe
strings) to display count and history, e.g., create two divs or reuse existing
elements and assign their textContent using the plugin reference instead of
assigning plugin.innerHTML.
packages/angular-devtools/provider/with-devtools.ts (2)

86-89: Plugins are set once and won't react to changes in devtoolsOptions().plugins.

config and eventBusConfig are wrapped in computed() to stay reactive, but plugins is set imperatively with plugins.set(...) only during initialization. If devtoolsOptions().plugins changes later, the devtools won't reflect those changes.

If this is intentional (plugins are expected to be static), consider adding a brief comment. Otherwise, consider using an effect() to keep plugins in sync:

Proposed fix if reactivity is needed
+      import { effect } from '@angular/core'
       // ...
-      plugins.set(devtoolsOptions().plugins ?? [])
+      effect(() => {
+        plugins.set(devtoolsOptions().plugins ?? [])
+      })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/angular-devtools/provider/with-devtools.ts` around lines 86 - 89,
The plugins array is only set once with plugins.set(devtoolsOptions().plugins ??
[]) and won't update when devtoolsOptions().plugins changes; change this to a
reactive effect that watches devtoolsOptions().plugins and calls
plugins.set(...) whenever it updates (or add a comment if immutability is
intentional). Specifically, replace the one-time call to plugins.set with an
effect/autorun that reads devtoolsOptions().plugins and updates plugins via
plugins.set(...) so it stays in sync with the computed config/eventBusConfig
used elsewhere.

32-34: Consider tightening the Array<any> types.

Both WithDevtoolsOptionsFn and WithDevtoolsOptions.deps use Array<any>, which loses type safety. If stronger typing isn't feasible due to Angular DI constraints, this is acceptable, but a brief JSDoc comment explaining why any is necessary would help future maintainers.

Also applies to: 56-58

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/angular-devtools/provider/with-devtools.ts` around lines 32 - 34,
Tighten the loose Array<any> usages by replacing them with a safer type (prefer
unknown[] or make WithDevtoolsOptionsFn generic like <Args extends
any[]>(...args: Args) => TanStackDevtoolsAllOptions and use Args for
WithDevtoolsOptions.deps) so callers retain type information; if Angular DI
constraints prevent stronger typing, add a short JSDoc above
WithDevtoolsOptionsFn and the WithDevtoolsOptions.deps field explaining why any
is necessary and referencing the DI limitation. Ensure you update the
declarations for WithDevtoolsOptionsFn and the WithDevtoolsOptions.deps usage
(the symbols named WithDevtoolsOptionsFn and WithDevtoolsOptions.deps)
consistently.
packages/devtools-utils/src/angular/panel.ts (1)

22-65: Consider adding JSDoc and explicit return type for public API clarity.

createAngularPanel is a public API but lacks documentation explaining its purpose, the tuple return structure, and why the second element is () => null. Adding a brief JSDoc (without @params/@returns per coding guidelines) and an explicit return type would improve discoverability.

Example documentation
+/**
+ * Creates an Angular panel adapter for TanStack Devtools.
+ * 
+ * Accepts either a CoreClass constructor directly or a lazy loader function
+ * that returns a Promise resolving to the constructor.
+ * 
+ * Returns a tuple where the first element is a factory for mounting the panel,
+ * and the second is a no-op placeholder for framework compatibility.
+ */
 export function createAngularPanel<
   TComponentProps extends DevtoolsPanelProps,
   TCoreDevtoolsClass extends BaseCorePanelClass,
 >(
   CoreClass:
     | CoreClassConstructor<TComponentProps, TCoreDevtoolsClass>
     | (() => Promise<
         CoreClassConstructor<TComponentProps, TCoreDevtoolsClass>
       >),
-) {
+): readonly [
+  () => (inputs: () => TComponentProps, host: HTMLElement) => () => void,
+  () => null,
+] {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/devtools-utils/src/angular/panel.ts` around lines 22 - 65, Add JSDoc
and an explicit return type to the public function createAngularPanel to
document its purpose, the tuple structure, and why the second tuple entry is ()
=> null; update the function signature to include a clear return type (e.g.,
readonly [ (inputs: () => TComponentProps, host: HTMLElement) => () => void, ()
=> null ]) and add a brief JSDoc block above createAngularPanel describing what
the factory returns, what the first tuple function does (mount/unmount via
CoreClass), and why the second is a stub; reference the createAngularPanel
function, the returned tuple and the second element (() => null) when making
these edits.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@examples/angular/with-devtools/src/app/devtools/custom-devtools-panel.ts`:
- Around line 13-23: The `@Component` metadata for the custom devtools panel
includes an imports array (JsonPipe) but is missing standalone: true, which is
required for using imports; update the Component decorator metadata (the
`@Component` applied to the custom devtool panel) to include standalone: true so
the imports property is valid and the Angular compiler will succeed.

In `@packages/angular-devtools/src/devtools.ts`:
- Around line 275-279: The current isClassConstructor wrongly treats plain
functions as classes; replace its heuristic with a reliable ES6-class check: if
o is a function, call Function.prototype.toString on it and test for a leading
"class" token (e.g. /^\s*class\b/), returning true only for that case. Update
the isClassConstructor<T> implementation (the function named isClassConstructor)
to use this toString/regex check so functional renderers are not misclassified
and therefore not handed to renderComponent().
- Around line 125-151: The spread usage ...(plugin.inputs ?? {}) drops
non-plain-object inputs (functions/signals); instead stop spreading and pass the
inputs value directly as a property so functions/signals are preserved. Update
the three call sites that build the options object — the calls to
this.renderComponent(plugin.name...), this.renderComponent(plugin.render...),
and this.#renderComponentFunction(...) — to construct options like { theme,
inputs: plugin.inputs ?? {} } (or { theme, inputs: plugin.inputs } if a default
empty object isn't desired) rather than spreading plugin.inputs.
- Around line 142-153: The async call to this.#renderComponentFunction with
plugin.render (typed as TanStackDevtoolsAngularPluginRender) can reject and
currently has no error handling; wrap the call in a try/catch (or attach a
.catch) to handle rejections, log the error (include plugin identity/context),
and optionally fall back or swallow to prevent unhandled promise
rejections—update the call site where this.#renderComponentFunction(...) is
invoked to catch and handle any thrown errors.

---

Minor comments:
In `@docs/framework/angular/adapter.md`:
- Around line 46-48: The sentence incorrectly refers to a non-existent
`component` field; update the documentation text to reference the actual
`render` field used in the adapter type and examples (e.g., change "uses
`component` (an Angular component class reference) instead of `render`" to
indicate that the adapter uses `render` and explain that inputs are provided via
the `inputs` field and bound with `setInput()`), or if the implementation should
instead use `component`, update the type/examples to match—ensure consistency
between the docs and the symbols `render`, `inputs`, and `setInput()`.
- Around line 38-45: The docs table incorrectly types the render field as
Type<any>; update the table entry for the `render` row to use the actual type
name `TanStackDevtoolsAngularPluginRender` and adjust the description to
reference that type (e.g., "The render property is a
TanStackDevtoolsAngularPluginRender that defines the Angular component/class
used for plugin panel content"); ensure the `render` column now matches the type
alias `TanStackDevtoolsAngularPluginRender` used in the code.

In `@docs/framework/angular/basic-setup.md`:
- Around line 18-32: The import name in the example is inconsistent: the file
imports TanStackDevtoolsComponent but the App component's standalone imports
array references TanStackDevtools. Fix by making the imported symbol match the
one used in the App component — either change the import to TanStackDevtools
(recommended) or change the imports array to use TanStackDevtoolsComponent so
the imported identifier and the imports entry are the same; update the import
statement near the top that currently names TanStackDevtoolsComponent
accordingly.

In `@docs/framework/angular/guides/custom-plugins.md`:
- Line 178: The phrase "built in" in the sentence referencing TanStackDevtools
and the TanStack EventClient should be hyphenated; update the sentence so it
reads "built-in debug mode" where "built in" appears (affecting the line
mentioning TanStackDevtools and EventClient) to correct the grammar.
- Line 197: Replace the incorrect two-word phrase "Where as" with the correct
single-word "Whereas" in the docs/framework/angular/guides/custom-plugins.md
content to fix the minor spelling error.
- Around line 213-216: The fenced code block showing debug output lacks a
language specifier; update the block in
docs/framework/angular/guides/custom-plugins.md around the debug output
containing "[tanstack-devtools:client-bus] Initializing client event bus" and
"[tanstack-devtools:custom-devtools-plugin] Registered event to bus
custom-devtools:counter-state" by adding a language tag (e.g., ```text) after
the opening backticks so the block becomes a fenced code block with the "text"
specifier for consistent formatting.
- Around line 122-135: The template incorrectly invokes the getter stateHistory
as a method; update the template to reference the getter without parentheses
(use {{ stateHistory }} instead of {{ stateHistory() }}) so it matches the
DevtoolPanel get stateHistory() accessor and returns the computed JSON string
properly.

In `@examples/angular/panel/.vscode/launch.json`:
- Around line 9-16: Update the VS Code launch configuration so the preLaunchTask
matches the project's package manager: replace the "preLaunchTask" values
referencing "npm: start" and "npm: test" with the corresponding "pnpm: start"
and "pnpm: test" (or ensure tasks with those names exist), targeting the entries
that define the Chrome launches ("name": "ng serve"/implicit block and "name":
"ng test") so VS Code will run the correct pnpm tasks before launching.

In `@examples/angular/panel/README.md`:
- Around line 1-3: The README title currently reads "Basic" but should match the
example directory name; update the top-level heading in the README.md by
replacing the heading text "Basic" with "Panel" so the file title reads "#
Panel" for consistency with the panel example directory.

In `@examples/angular/panel/src/index.html`:
- Line 5: Update the HTML title in examples/angular/panel/src/index.html:
replace the current <title>Basic</title> with a more accurate title reflecting
the app (for example "Angular Panel Example" or "Panel") so the browser tab and
metadata correctly identify this panel demo; modify the <title> tag content only
to match the panel example name.

In `@examples/angular/with-devtools/.editorconfig`:
- Around line 11-17: Remove the redundant IntelliJ-specific TypeScript setting
by deleting the line "ij_typescript_use_double_quotes = false" from the file;
keep the standard "[*.ts]" block with "quote_type = single" as the single source
of truth for quote style and remove only the "ij_typescript_use_double_quotes"
entry so there is no duplicate configuration.

In `@examples/angular/with-devtools/angular.json`:
- Around line 12-13: The project key in the angular.json "projects" object is
incorrectly named "basic" but should match the example folder; rename the
project key from "basic" to "with-devtools" and update every reference to that
key inside the file (notably the build and serve targets currently referencing
"basic") so they point to "with-devtools" instead; ensure target entries such as
the "build" and "serve" (and any "architect" or "targets" blocks) are updated to
use the new project name so configuration and CLI commands remain consistent.

In `@examples/angular/with-devtools/README.md`:
- Around line 1-3: The README title currently reads "Basic" which doesn't match
this example's directory; update the top-level heading in
examples/angular/with-devtools/README.md (the line containing "# Basic") to a
correct, descriptive title such as "# with-devtools" or "# Angular with
DevTools" so the file name and content reflect the example's purpose.

In `@examples/angular/with-devtools/src/index.html`:
- Line 5: The page title in index.html is incorrect ("Basic"); update the
<title> element in the file so it matches the example name ("with-devtools" or a
human-friendly variant like "With Devtools") to avoid the copy-paste artifact
and ensure the example metadata/title reflects this example.

In `@packages/angular-devtools/provider/with-devtools.ts`:
- Around line 75-80: Replace the direct string comparison of PLATFORM_ID with
Angular's isPlatformBrowser check: import isPlatformBrowser from
'@angular/common' and change the platform guard in the
provideEnvironmentInitializer block (where inject(PLATFORM_ID) is used) to use
if (!isPlatformBrowser(inject(PLATFORM_ID))) return; so the environment check
uses the documented API rather than comparing to the string 'browser'.
- Around line 91-103: The dynamic import of '@tanstack/angular-devtools' in
with-devtools.ts lacks error handling; update the import promise chain that
calls import('@tanstack/angular-devtools').then(...) to add a .catch handler
that logs the error and a contextual message (e.g., "Failed to load
TanStackDevtools") and avoids leaving an unhandled rejection; reference the
import call and the createComponent/TanStackDevtools block so the catch can
report the error and optionally perform a safe fallback (e.g., skip attaching
the devtools) if the module fails to load.

In `@packages/angular-devtools/src/types.ts`:
- Around line 44-62: The JSDoc for TanStackDevtoolsAngularInit and its example
incorrectly refer to a `component` property while the actual type
TanStackDevtoolsAngularPlugin defines `render`; update the JSDoc/example to use
`render` (e.g., replace `{ name: 'My Plugin', component: MyPluginComponent }`
with `{ name: 'My Plugin', render: MyPluginComponent }`) so the documentation
matches the TanStackDevtoolsAngularPlugin type and avoids confusion.

In `@packages/devtools-utils/src/angular/panel.ts`:
- Around line 53-57: The lazy-load branch calling CoreClass() lacks error
handling; wrap the promise chain starting at CoreClass() so rejections are
caught and handled—add a .catch handler to the CoreClass().then(...).then(mount)
chain that logs the error and/or invokes the existing mount/error handling flow
(reference CoreClass, inputs(), and mount) to avoid unhandled promise rejections
and surface failures during dynamic import.
- Around line 15-20: The type-guard isPanelClassConstructor currently uses
!!o.prototype which misclassifies plain functions as class constructors; replace
that heuristic with a robust check that ensures o is an ES6 class constructor,
e.g. verify typeof o === 'function' and use Function.prototype.toString.call(o)
to test for a leading "class" token (e.g. /^class\s/.test(...)); update the
function isPanelClassConstructor<TComponentProps, TCoreDevtoolsClass> to use
this test (or, if you intentionally only accept arrow/async factories, add a
clear comment explaining that assumption) so non-class functions are not treated
as constructors.

---

Nitpick comments:
In `@docs/framework/angular/basic-setup.md`:
- Around line 51-58: The examples use inconsistent class names — one declares
class App and the later snippet declares class AppComponent; update the later
example so the class name matches the first example (e.g., rename AppComponent
to App) and ensure the export and any internal references (plugins array,
TanStackDevtoolsAngularPlugin, AngularQueryDevtoolsPanel) are updated
accordingly so both examples use the same class identifier.

In `@examples/angular/basic/src/app/app.ts`:
- Around line 19-21: The `@defer` block uses an explicit always-true condition
(`@defer` (when true)) which is redundant; change it to use the simpler
unconditional defer syntax or a meaningful predicate instead. Update the `@defer`
usage wrapping <tanstack-devtools [plugins]="plugins()" [eventBusConfig]="{
debug: true }" /> by removing "(when true)" or replacing it with a real trigger
(e.g., a boolean like isIdle or isDevMode) so the intent is clear and the defer
behavior for tanstack-devtools and plugins() is preserved.

In `@examples/angular/basic/src/app/devtools/custom-devtools-panel.ts`:
- Around line 27-28: The "// Automatically added" comment above the readonly
theme = input<string>() declaration is ambiguous; either replace it with a
clearer note that states who/what adds this input and when (e.g., "Added by
DevTools host at runtime" or "Injected by framework build step") or remove the
comment entirely if it's stale; update the comment adjacent to the readonly
theme = input<string>() symbol to explicitly state its source and purpose so
future readers understand why the input exists.

In `@examples/angular/panel/package.json`:
- Line 22: The dependency "@tanstack/devtools-event-client" is pinned to an
exact version "0.4.2" while other `@tanstack` packages in package.json use caret
ranges; update the version specifier for "@tanstack/devtools-event-client" to
use a caret range (e.g. "^0.4.2") to match the other `@tanstack/`* entries and
maintain consistent semver behavior across the project.

In `@examples/angular/panel/src/app/counter.ts`:
- Around line 10-28: increment and decrement both call
DevtoolsEventClient.emit('counter-state', { count: count(), history }) causing
duplicated emit logic; extract that into a small helper (e.g., emitCounterState
or notifyCounterState) that reads count() and history and calls
DevtoolsEventClient.emit('counter-state', payload), then replace the duplicated
emit calls in the increment and decrement functions with a single call to the
new helper; ensure the helper is colocated with the count/history variables and
used after updating history so payload stays correct.
- Around line 4-9: createCounter currently never emits its initial value so the
UI sees no state until the first increment/decrement; fix it by emitting the
initial state when the counter is created: after initializing count and history
in createCounter, record the initial value (e.g., push count() into history) and
invoke the same notifier/emit function you use for subsequent updates (or
expose/ call value() once) so the panel receives the initial state; update
references in createCounter, count, history and the notifier/emit handler
accordingly.

In `@examples/angular/panel/src/app/devtools/custom-devtools-panel.ts`:
- Around line 12-17: The render(count: number, history: string) function writes
unescaped HTML into plugin via plugin.innerHTML which static analysis flagged;
change render to avoid innerHTML by creating or selecting child elements under
the plugin container and setting their textContent (or concatenating safe
strings) to display count and history, e.g., create two divs or reuse existing
elements and assign their textContent using the plugin reference instead of
assigning plugin.innerHTML.

In `@examples/angular/with-devtools/.vscode/mcp.json`:
- Line 6: The current args entry ["-y", "@angular/cli", "mcp"] intentionally
uses an unpinned `@angular/cli` per Angular's MCP docs—leave it as-is to track
latest fixes; if you instead want reproducible behavior across the team, change
that args element to "@angular/cli@20" (i.e., replace "@angular/cli" with
"@angular/cli@20") so the MCP launch uses the pinned CLI version.

In `@packages/angular-devtools/package.json`:
- Around line 55-63: Remove the duplicate tslib entry by keeping it under
"dependencies" and deleting the "tslib" entry from "devDependencies" in
package.json; update the package.json object so only the top-level
"dependencies" contains "tslib" and ensure no other references to tslib remain
in dev tooling config (look for "tslib" in the package.json
dependencies/devDependencies blocks).
- Line 53: The build script in package.json ("build") uses Unix-only rm -rf;
replace those calls with a cross-platform remover such as rimraf (or reuse the
existing "premove" helper used by "clean") so Windows contributors won't fail;
update the "build" script to call rimraf on ./dist/package.json and
./dist/provider/package.json (or call the existing preremove command), and if
using rimraf add it to devDependencies and/or the scripts so CI and local
installs have the tool available.

In `@packages/angular-devtools/provider/with-devtools.ts`:
- Around line 86-89: The plugins array is only set once with
plugins.set(devtoolsOptions().plugins ?? []) and won't update when
devtoolsOptions().plugins changes; change this to a reactive effect that watches
devtoolsOptions().plugins and calls plugins.set(...) whenever it updates (or add
a comment if immutability is intentional). Specifically, replace the one-time
call to plugins.set with an effect/autorun that reads devtoolsOptions().plugins
and updates plugins via plugins.set(...) so it stays in sync with the computed
config/eventBusConfig used elsewhere.
- Around line 32-34: Tighten the loose Array<any> usages by replacing them with
a safer type (prefer unknown[] or make WithDevtoolsOptionsFn generic like <Args
extends any[]>(...args: Args) => TanStackDevtoolsAllOptions and use Args for
WithDevtoolsOptions.deps) so callers retain type information; if Angular DI
constraints prevent stronger typing, add a short JSDoc above
WithDevtoolsOptionsFn and the WithDevtoolsOptions.deps field explaining why any
is necessary and referencing the DI limitation. Ensure you update the
declarations for WithDevtoolsOptionsFn and the WithDevtoolsOptions.deps usage
(the symbols named WithDevtoolsOptionsFn and WithDevtoolsOptions.deps)
consistently.

In `@packages/devtools-utils/src/angular/panel.ts`:
- Around line 22-65: Add JSDoc and an explicit return type to the public
function createAngularPanel to document its purpose, the tuple structure, and
why the second tuple entry is () => null; update the function signature to
include a clear return type (e.g., readonly [ (inputs: () => TComponentProps,
host: HTMLElement) => () => void, () => null ]) and add a brief JSDoc block
above createAngularPanel describing what the factory returns, what the first
tuple function does (mount/unmount via CoreClass), and why the second is a stub;
reference the createAngularPanel function, the returned tuple and the second
element (() => null) when making these edits.

In `@packages/devtools-utils/vite.config.angular.ts`:
- Around line 5-7: The config object created via defineConfig currently includes
an unnecessary empty "plugins" property; remove the "plugins: []" entry from the
defineConfig call (the config constant) to clean up the Vite configuration
without changing behavior—locate the defineConfig({ ... }) block that assigns to
config and delete the plugins key.

Comment on lines +13 to +23
@Component({
selector: `custom-devtool-panel`,
template: `
<div>
<div>counter state: {{ state()?.count }}</div>
<div>counter history: {{ state()?.history | json }}</div>
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [JsonPipe],
})
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify that the component metadata uses `imports` and whether `standalone: true` is present.
python - <<'PY'
from pathlib import Path
import re

path = Path("examples/angular/with-devtools/src/app/devtools/custom-devtools-panel.ts")
text = path.read_text()
match = re.search(r'@Component\(\{(?P<body>.*?)\}\)', text, re.S)
body = match.group('body') if match else ''
print("imports_present =", "imports:" in body)
print("standalone_true =", bool(re.search(r'standalone\s*:\s*true', body)))
PY

Repository: TanStack/devtools

Length of output: 107


Add standalone: true to the component metadata.

Line 22 uses imports, which is only valid for standalone components. Without standalone: true, Angular compilation will fail.

Proposed fix
 `@Component`({
   selector: `custom-devtool-panel`,
+  standalone: true,
   template: `
     <div>
       <div>counter state: {{ state()?.count }}</div>
       <div>counter history: {{ state()?.history | json }}</div>
     </div>
   `,
   changeDetection: ChangeDetectionStrategy.OnPush,
   imports: [JsonPipe],
 })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Component({
selector: `custom-devtool-panel`,
template: `
<div>
<div>counter state: {{ state()?.count }}</div>
<div>counter history: {{ state()?.history | json }}</div>
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [JsonPipe],
})
`@Component`({
selector: `custom-devtool-panel`,
standalone: true,
template: `
<div>
<div>counter state: {{ state()?.count }}</div>
<div>counter history: {{ state()?.history | json }}</div>
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [JsonPipe],
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/angular/with-devtools/src/app/devtools/custom-devtools-panel.ts`
around lines 13 - 23, The `@Component` metadata for the custom devtools panel
includes an imports array (JsonPipe) but is missing standalone: true, which is
required for using imports; update the Component decorator metadata (the
`@Component` applied to the custom devtool panel) to include standalone: true so
the imports property is valid and the Angular compiler will succeed.

Comment on lines +125 to +151
this.renderComponent(plugin.name as Type<any>, e, {
theme,
...(plugin.inputs ?? {}),
})
},
render: (e, theme) => {
if (!plugin.render) {
return
}

runInInjectionContext(this.#viewInjector, () => {
if (isClassConstructor<unknown>(plugin.render)) {
this.renderComponent(plugin.render, e, {
theme,
...(plugin.inputs ?? {}),
})
} else {
this.#renderComponentFunction(
plugin.render as Exclude<
TanStackDevtoolsAngularPluginRender,
Type<any> | null
>,
e,
{
theme,
...(plugin.inputs ?? {}),
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don’t spread plugin.inputs; it breaks function/signal inputs.

At Line 127/139/150, ...(plugin.inputs ?? {}) only works for plain objects. If plugin.inputs is a function/signal, inputs are dropped and reactivity is lost.

💡 Proposed fix
   private convertPlugin(
     plugin: TanStackDevtoolsAngularPlugin,
   ): TanStackDevtoolsPlugin {
+    const pluginInputs =
+      plugin.inputs ? this.#normalizeInputs(plugin.inputs as any) : () => ({})
+
     return {
       id: plugin.id,
       defaultOpen: plugin.defaultOpen,
       name:
         typeof plugin.name === 'string'
           ? plugin.name
           : (e, theme) => {
-              this.renderComponent(plugin.name as Type<any>, e, {
-                theme,
-                ...(plugin.inputs ?? {}),
-              })
+              this.renderComponent(plugin.name as Type<any>, e, () => ({
+                theme,
+                ...pluginInputs(),
+              }))
             },
       render: (e, theme) => {
         if (!plugin.render) {
           return
         }
@@
           if (isClassConstructor<unknown>(plugin.render)) {
-            this.renderComponent(plugin.render, e, {
-              theme,
-              ...(plugin.inputs ?? {}),
-            })
+            this.renderComponent(plugin.render, e, () => ({
+              theme,
+              ...pluginInputs(),
+            }))
           } else {
             this.#renderComponentFunction(
               plugin.render as Exclude<
                 TanStackDevtoolsAngularPluginRender,
                 Type<any> | null
               >,
               e,
-              {
-                theme,
-                ...(plugin.inputs ?? {}),
-              },
+              () => ({
+                theme,
+                ...pluginInputs(),
+              }),
             )
           }
         })
       },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/angular-devtools/src/devtools.ts` around lines 125 - 151, The spread
usage ...(plugin.inputs ?? {}) drops non-plain-object inputs
(functions/signals); instead stop spreading and pass the inputs value directly
as a property so functions/signals are preserved. Update the three call sites
that build the options object — the calls to
this.renderComponent(plugin.name...), this.renderComponent(plugin.render...),
and this.#renderComponentFunction(...) — to construct options like { theme,
inputs: plugin.inputs ?? {} } (or { theme, inputs: plugin.inputs } if a default
empty object isn't desired) rather than spreading plugin.inputs.

Comment on lines +142 to +153
this.#renderComponentFunction(
plugin.render as Exclude<
TanStackDevtoolsAngularPluginRender,
Type<any> | null
>,
e,
{
theme,
...(plugin.inputs ?? {}),
},
)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's locate and examine the file
git ls-files "packages/angular-devtools/src/devtools.ts"

Repository: TanStack/devtools

Length of output: 102


🏁 Script executed:

# Check file size before reading
wc -l packages/angular-devtools/src/devtools.ts

Repository: TanStack/devtools

Length of output: 106


🏁 Script executed:

# Read the relevant section around lines 142-153
sed -n '130,160p' packages/angular-devtools/src/devtools.ts

Repository: TanStack/devtools

Length of output: 892


🏁 Script executed:

# Find the `#renderComponentFunction` definition
rg "#renderComponentFunction" packages/angular-devtools/src/ -A 15 -B 2

Repository: TanStack/devtools

Length of output: 2697


🏁 Script executed:

# Let's see more of the `#renderComponentFunction` implementation to understand it better
sed -n '260,279p' packages/angular-devtools/src/devtools.ts

Repository: TanStack/devtools

Length of output: 484


🏁 Script executed:

# Check if there's any error handling at a higher level (in the render callback)
sed -n '120,170p' packages/angular-devtools/src/devtools.ts

Repository: TanStack/devtools

Length of output: 1495


🏁 Script executed:

# Search for any try-catch or error handling patterns in the file
rg "catch|error|Error" packages/angular-devtools/src/devtools.ts -B 2 -A 2

Repository: TanStack/devtools

Length of output: 255


🏁 Script executed:

# Let's see the full `#renderComponentFunction` implementation to understand what it does
sed -n '201,245p' packages/angular-devtools/src/devtools.ts

Repository: TanStack/devtools

Length of output: 1369


🏁 Script executed:

# Check how renderComponent method handles errors (to compare with renderComponentFunction)
rg "renderComponent\(" packages/angular-devtools/src/devtools.ts -B 2 -A 10 | head -50

Repository: TanStack/devtools

Length of output: 1249


🏁 Script executed:

# Check if there's any outer promise handling for the plugin system
rg "plugin\.render|render.*callback" packages/angular-devtools/src/ -B 3 -A 3

Repository: TanStack/devtools

Length of output: 1430


🏁 Script executed:

# Let me see the full `#renderComponentFunction` to understand what can be thrown/rejected
sed -n '201,235p' packages/angular-devtools/src/devtools.ts

Repository: TanStack/devtools

Length of output: 1077


🏁 Script executed:

# Check TypeScript config to understand strict mode settings
cat tsconfig.json 2>/dev/null | head -40

Repository: TanStack/devtools

Length of output: 921


🏁 Script executed:

# Let me get the exact lines 142-153 with more context
sed -n '135,160p' packages/angular-devtools/src/devtools.ts | cat -n

Repository: TanStack/devtools

Length of output: 986


🏁 Script executed:

# Verify the full async method to see what exceptions it can throw
sed -n '201,240p' packages/angular-devtools/src/devtools.ts | cat -n

Repository: TanStack/devtools

Length of output: 1580


🏁 Script executed:

# Let me get the exact line numbers for the `#renderComponentFunction` definition
rg "async `#renderComponentFunction`" packages/angular-devtools/src/devtools.ts -n

Repository: TanStack/devtools

Length of output: 98


🏁 Script executed:

# Check the complete `#renderComponentFunction` to see all possible error paths
sed -n '201,250p' packages/angular-devtools/src/devtools.ts | cat -n

Repository: TanStack/devtools

Length of output: 1892


🏁 Script executed:

# Get the actual `#renderComponentFunction` implementation (line 170 onwards)
sed -n '170,200p' packages/angular-devtools/src/devtools.ts | cat -n

Repository: TanStack/devtools

Length of output: 1356


🏁 Script executed:

# Verify the actual lines where the call happens (line 142 based on the review)
sed -n '138,155p' packages/angular-devtools/src/devtools.ts | cat -n

Repository: TanStack/devtools

Length of output: 628


🏁 Script executed:

# Check if there's been any recent changes or different version of error handling
git log --oneline packages/angular-devtools/src/devtools.ts | head -5

Repository: TanStack/devtools

Length of output: 137


Add error handling to async render call to prevent unhandled promise rejection.

The #renderComponentFunction method at line 142 is async and can reject when the lazy import or rendering fails, but the call site has no error handling. An unhandled rejection will bubble and can cause issues in strict environments.

Suggested fix
-            this.#renderComponentFunction(
+            void this.#renderComponentFunction(
               plugin.render as Exclude<
                 TanStackDevtoolsAngularPluginRender,
                 Type<any> | null
               >,
               e,
               {
                 theme,
                 ...(plugin.inputs ?? {}),
               },
-            )
+            ).catch((error) => {
+              console.error(
+                '[`@tanstack-devtools/angular`] Failed to render plugin',
+                error,
+              )
+            })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
this.#renderComponentFunction(
plugin.render as Exclude<
TanStackDevtoolsAngularPluginRender,
Type<any> | null
>,
e,
{
theme,
...(plugin.inputs ?? {}),
},
)
}
void this.#renderComponentFunction(
plugin.render as Exclude<
TanStackDevtoolsAngularPluginRender,
Type<any> | null
>,
e,
{
theme,
...(plugin.inputs ?? {}),
},
).catch((error) => {
console.error(
'[`@tanstack-devtools/angular`] Failed to render plugin',
error,
)
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/angular-devtools/src/devtools.ts` around lines 142 - 153, The async
call to this.#renderComponentFunction with plugin.render (typed as
TanStackDevtoolsAngularPluginRender) can reject and currently has no error
handling; wrap the call in a try/catch (or attach a .catch) to handle
rejections, log the error (include plugin identity/context), and optionally fall
back or swallow to prevent unhandled promise rejections—update the call site
where this.#renderComponentFunction(...) is invoked to catch and handle any
thrown errors.

Comment on lines +275 to +279
function isClassConstructor<T>(
o: unknown,
): o is new (...args: Array<any>) => T {
return typeof o === 'function' && !!o.prototype && !!o.prototype.constructor
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cd packages/angular-devtools && head -300 src/devtools.ts | tail -50

Repository: TanStack/devtools

Length of output: 1395


🏁 Script executed:

cat -n packages/angular-devtools/src/devtools.ts | sed -n '270,285p'

Repository: TanStack/devtools

Length of output: 343


🏁 Script executed:

rg "isClassConstructor" packages/angular-devtools/

Repository: TanStack/devtools

Length of output: 339


🏁 Script executed:

cat -n packages/angular-devtools/src/devtools.ts | sed -n '200,230p'

Repository: TanStack/devtools

Length of output: 1296


🏁 Script executed:

rg -B 5 -A 5 "isClassConstructor" packages/angular-devtools/src/devtools.ts | head -30

Repository: TanStack/devtools

Length of output: 874


🏁 Script executed:

python3 << 'EOF'
# Test both implementations to understand the issue

# Current implementation simulation
def current_check(obj):
    """Simulates: typeof o === 'function' && !!o.prototype && !!o.prototype.constructor"""
    return callable(obj) and hasattr(obj, '__dict__') and hasattr(type(obj), '__init__')

# Proposed implementation would use regex on toString representation
def proposed_check(obj):
    """Simulates: /^class\s/.test(Function.prototype.toString.call(o))"""
    try:
        obj_str = str(obj)
        return callable(obj) and obj_str.startswith('<built-in') == False
    except:
        return False

# Test cases
class MyClass:
    pass

def my_function():
    pass

def another_func():
    return "test"

arrow_func = lambda x: x

# In JavaScript context, let's think about what the functions check:
# Current: typeof o === 'function' && !!o.prototype && !!o.prototype.constructor
# - In JS, both regular functions and classes have .prototype and .prototype.constructor
# - This means BOTH would return true, which is the bug

# Proposed: /^class\s/.test(Function.prototype.toString.call(o))
# - In JS, class toString() starts with "class"
# - Function toString() starts with "function"
# - This would correctly distinguish them

print("Testing JavaScript-like behavior:")
print("\nIn JavaScript:")
print("- Regular function has .prototype:", True)
print("- Regular function has .prototype.constructor:", True)
print("- Class has .prototype:", True)
print("- Class has .prototype.constructor:", True)
print("\nConclusion: Current check cannot distinguish regular functions from classes")
print("\nString representation:")
print("- Regular function toString(): 'function myFunc() { ... }'")
print("- Class toString(): 'class MyClass { ... }'")
print("\nConclusion: Proposed fix correctly distinguishes them via regex")
EOF

Repository: TanStack/devtools

Length of output: 541


🏁 Script executed:

cat -n packages/angular-devtools/src/devtools.ts | sed -n '95,145p'

Repository: TanStack/devtools

Length of output: 1800


isClassConstructor cannot distinguish regular functions from class constructors.

The current implementation checks for typeof o === 'function' && !!o.prototype && !!o.prototype.constructor, but both regular functions and class constructors have these properties in JavaScript. This causes functional renderers to be misclassified as component classes and routed to renderComponent() (line 136, 154), causing runtime failures.

Proposed fix
 function isClassConstructor<T>(
   o: unknown,
 ): o is new (...args: Array<any>) => T {
-  return typeof o === 'function' && !!o.prototype && !!o.prototype.constructor
+  return (
+    typeof o === 'function' &&
+    /^class\s/.test(Function.prototype.toString.call(o))
+  )
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/angular-devtools/src/devtools.ts` around lines 275 - 279, The
current isClassConstructor wrongly treats plain functions as classes; replace
its heuristic with a reliable ES6-class check: if o is a function, call
Function.prototype.toString on it and test for a leading "class" token (e.g.
/^\s*class\b/), returning true only for that case. Update the
isClassConstructor<T> implementation (the function named isClassConstructor) to
use this toString/regex check so functional renderers are not misclassified and
therefore not handed to renderComponent().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants